package munci.core;

import munci.read.Token;
import munci.read.Tokenizer;

public class Context {
	
	private static final ThreadLocal<Context> local = new ThreadLocal<Context>() {
		@Override protected Context initialValue() {
			return new Context();
		}
	};
	
	public static Context get() {
		return local.get();
	}
	
	private Dictionary dict = new Dictionary();
	private Stack stack = new Stack();
	
	public Dictionary dict() {
		return dict;
	}
	
	public void pushDict() {
		dict = new Dictionary(dict);
	}
	
	public void popDict() {
		dict = dict.next();
	}
	
	public Stack stack() {
		return stack;
	}
	
	private Tokenizer tokens;
	
	private boolean compiling = false;
	
	private Dictionary.Entry executing = null;
	
	public void nextToken() {
		tokens.nextToken();
	}
	
	public Token token() {
		return tokens.token();
	}
	
	public void setCompiling(boolean val) {
		this.compiling = val;
	}
	
	public boolean compiling() {
		return compiling;
	}
	
	public void call(final String name) {
		Dictionary.Entry entry = dict.find(name, stack.canInvoke);

		if (compiling() && (entry == null || !entry.word.hasAttribute(Word.IMMEDIATE))) {
			entry = dict.find(name, stack.canCompile);
			if (entry != null && !entry.word.hasAttribute(Word.IMMEDIATE)) {
				stack.compile(entry.reference);
				return;
			}
			entry = null;
		}
		
		if (entry == null) {
			throw new RuntimeException("Word not found: " + name);
		}
		
		try {
			executing = entry;
			stack.invoke(entry.word);
		} catch (Throwable e) {
			throw new RuntimeException("Calling: " + entry.name, e);
		} finally {
			executing = null;
		}
	}
	
	public void interpret(Tokenizer toks) {
		Tokenizer prev = tokens;
		tokens = toks;
		try {
			Token tok;
			while (null != (tok = token())) {
				if (tok instanceof Token.Literal) {
					stack.push(tok, tok.getClass());
				}
				nextToken();
				call(tok.id());
			}
		} finally {
			tokens = prev;
		}
	}
	
	@Override public String toString() {
		StringBuilder str = new StringBuilder();
		str.append(compiling()? "compiling" : "interpret");
		if (executing != null) {
			str.append(' ').append(executing.name);
		}
		return str.toString();
	}
}
